package io.contract.common.utils;

import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okio.Buffer;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * OkHttp帮助类，封装了常用的操作
 * <p>
 * Created on 2019-02-12.
 */
public class OkHttpHelper {
    private static OkHttpClient client;
    // 默认的获取连接的超时时间, 单位s
    private static final int DEFAULT_CONNECTION_TIMEOUT = 300;
    // 默认连接超时, 单位s
    private static final int DEFAULT_READ_TIMEOUT = 300;
    // 默认的SOCKET读取超时时间, 单位s
    private static final int DEFAULT_WRITE_TIMEOUT = 300;
    // 默认的维护最大HTTP连接数
    private static final int DEFAULT_MAX_CONNECTIONS_COUNT = 1024;
    private static final String HTTP_PREFIX = "http://";
    private static final String HTTPS_PREFIX = "https://";

    static {
        init(DEFAULT_MAX_CONNECTIONS_COUNT, DEFAULT_CONNECTION_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT);
    }

    /**
     * 初始化
     *
     * @param maxTotal       最大连接数
     * @param connectTimeout 连接超时
     * @param readTimeout    读超时
     * @param writeTimeout   写超时
     */
    public static void init(int maxTotal, int connectTimeout, int readTimeout, int writeTimeout) {
        Dispatcher dispatcher = new Dispatcher();
        dispatcher.setMaxRequests(maxTotal);
        dispatcher.setMaxRequestsPerHost(maxTotal);
        ConnectionPool connectionPool = new ConnectionPool(32, 5, TimeUnit.MINUTES);
        client = new OkHttpClient.Builder()
                .dispatcher(dispatcher)
                .addInterceptor(new LogInterceptor())
                .connectionPool(connectionPool)
                .connectTimeout(connectTimeout, TimeUnit.SECONDS)
                .readTimeout(readTimeout, TimeUnit.SECONDS)
                .writeTimeout(writeTimeout, TimeUnit.SECONDS)
                .build();
    }

    public static Response get(String url) throws IOException {
        return get(url, null, null, null, null);
    }

    public static Response get(String url, Map<String, String> params) throws IOException {
        return get(url, params, null, null, null);
    }

    public static Response get(String url, Map<String, String> params, Map<String, String> headers, String hostname, Integer port) throws IOException {
        url = addScheme(url);
        params = nullToEmpty(params);
        headers = nullToEmpty(headers);
        HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
        }
        HttpUrl httpUrl = urlBuilder.build();
        Request request = new Request.Builder()
                .url(httpUrl)
                .headers(Headers.of(headers))
                .get()
                .build();
        Dispatcher dispatcher = new Dispatcher();
        dispatcher.setMaxRequests(DEFAULT_MAX_CONNECTIONS_COUNT);
        dispatcher.setMaxRequestsPerHost(DEFAULT_MAX_CONNECTIONS_COUNT);
        ConnectionPool connectionPool = new ConnectionPool(32, 50, TimeUnit.MINUTES);
        OkHttpClient okHttpClient;
        if (StringUtils.isNotBlank(hostname) && port != null) {
            Proxy proxy = new Proxy(Proxy.Type.HTTP,
                    new InetSocketAddress(hostname, port));
            okHttpClient = new OkHttpClient.Builder().proxy(proxy)
                    .dispatcher(dispatcher)
                    .addInterceptor(new LogInterceptor())
                    .connectionPool(connectionPool)
                    .connectTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
                    .build();
        } else {
            okHttpClient = new OkHttpClient.Builder()
                    .dispatcher(dispatcher)
                    .addInterceptor(new LogInterceptor())
                    .connectionPool(connectionPool)
                    .connectTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
                    .build();
        }
        return okHttpClient.newCall(request).execute();
    }

    public static Response post(String url) throws IOException {
        return post(url, (Map<String, String>) null, null);
    }

    public static Response post(String url, Map<String, String> params) throws IOException {
        return post(url, params, null);
    }

    public static Response post(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
        FormBody body = buildForm(params);
        return doPost(body, url, headers);
    }

    public static Response postJson(String url, String json, Map<String, String> headers) throws IOException {
        RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
        return doPost(body, url, headers);
    }

    public static Response postJson(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
        params = nullToEmpty(params);
        String json = JSON.toJSONString(params);
        return postJson(url, json, headers);
    }

    public static Response post(String url, byte[] data) throws IOException {
        return post(url, data, null);
    }

    public static Response post(String url, byte[] data, Map<String, String> headers) throws IOException {
        RequestBody body = RequestBody.create(MediaType.parse("application/octet-stream"), data);
        return doPost(body, url, headers);
    }

    private static Response doPost(RequestBody body, String url, Map<String, String> headers) throws IOException {
        url = addScheme(url);
        headers = nullToEmpty(headers);
        Request request = new Request.Builder()
                .url(url)
                .headers(Headers.of(headers))
                .post(body)
                .build();
        return client.newCall(request).execute();
    }

    public static Response put(String url) throws IOException {
        return put(url, null, null);
    }

    public static Response put(String url, Map<String, String> params) throws IOException {
        return put(url, params, null);
    }

    public static Response put(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
        url = addScheme(url);
        headers = nullToEmpty(headers);
        Request request = new Request.Builder()
                .url(url)
                .headers(Headers.of(headers))
                .put(buildForm(params))
                .build();
        return client.newCall(request).execute();
    }


    public static Response delete(String url) throws IOException {
        return delete(url, null, null);
    }

    public static Response delete(String url, Map<String, String> params) throws IOException {
        return delete(url, params, null);
    }

    public static Response delete(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
        headers = nullToEmpty(headers);
        url = addScheme(url);
        Request request = new Request.Builder()
                .url(url)
                .headers(Headers.of(headers))
                .delete(buildForm(params))
                .build();
        return client.newCall(request).execute();
    }

    public static FormBody buildForm(Map<String, String> params) {
        params = nullToEmpty(params);
        FormBody.Builder bodyBuilder = new FormBody.Builder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            bodyBuilder.add(entry.getKey(), entry.getValue());
        }
        return bodyBuilder.build();
    }

    public static String getString(Response response) throws IOException {
        if (null == response) {
            throw new NullPointerException("null == response");
        }
        return response.body().string();
    }

    public static byte[] getBytes(Response response) throws IOException {
        if (null == response) {
            throw new NullPointerException("null == response");
        }
        return response.body().bytes();
    }


    /**
     * 如果url头部不包含协议字段，则在url头部增加网络协议字段
     *
     * @param url    原始url
     * @param secure 是否使用安全协议
     * @return
     */
    public static String addScheme(String url, boolean secure) {
        if (!url.startsWith(HTTPS_PREFIX) && !url.startsWith(HTTP_PREFIX)) {
            String prefix = secure ? HTTPS_PREFIX : HTTP_PREFIX;
            return prefix + url;
        }
        return url;
    }

    public static String addScheme(String url) {
        return addScheme(url, false);
    }

    private static Map<String, String> emptyMap() {
        return new HashMap<>();
    }

    private static Map<String, String> nullToEmpty(Map<String, String> map) {
        return null == map ? new HashMap<String, String>() : map;
    }

    @Slf4j
    static class LogInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            long start = System.currentTimeMillis();
            Request request = chain.request();
            Response response = chain.proceed(request);
            HttpUrl httpUrl = request.url();
            Map<String, Object> getParams = new HashMap<>();
            Map<String, Object> headers = new HashMap<>();
            Set<String> paramKeys = httpUrl.queryParameterNames();
            for (String key : paramKeys) {
                getParams.put(key, httpUrl.queryParameter(key));
            }
            for (String head : request.headers().names()) {
                headers.put(head, request.headers().get(head));
            }
            String bodyParam = null;
            if (null != request.body() && request.body().contentType().toString().contains("json")) {
                Buffer buffer = new Buffer();
                request.body().writeTo(buffer);
                bodyParam = buffer.readUtf8();
            }
            String body = null;
            if (null != response.body()) {
                body = response.body().source().buffer().clone().toString();
            }
            log(httpUrl.url().toString(), getParams, bodyParam, headers, body, System.currentTimeMillis() - start);
            return response;

        }

        private void log(String url, Map<String, Object> queryParams, String bodyParams, Map<String, Object> headers, String response, long cost) {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("call api:%s cost:%s", url, cost));
            if (null != queryParams && !queryParams.isEmpty()) {
                try {
                    sb.append(String.format(" query:%s", JSON.toJSONString(queryParams)));
                } catch (Exception e) {
                    sb.append(String.format(" query:%s", e.getMessage()));
                }
            }
            if (StringUtils.isNotEmpty(bodyParams)) {
                sb.append(String.format(" body:%s", bodyParams));
            }
            if (null != headers && !headers.isEmpty()) {
                try {
                    sb.append(String.format(" headers:%s", JSON.toJSONString(headers)));
                } catch (Exception e) {
                    sb.append(String.format(" headers:%s", e.getMessage()));
                }
            }
            if (StringUtils.isNotEmpty(response)) {
                sb.append(String.format(" response:%s", response));
            }
            log.info(sb.toString());
        }
    }
}